use std::net::SocketAddr;
use std::path::PathBuf;
use std::process::Command;
use std::sync::Arc;
use time::macros::format_description;

use clap::Parser;
use fuse3::raw::Session;
use fuse3::MountOptions;
use time::UtcOffset;
use tokio::fs::File;

use tracing::{debug, error, info, trace, warn};
use tracing_subscriber::fmt::time::LocalTime;

use filesystem::VFStorage;
use metadata::Attribute;
use schedule::Scheduler;

mod error;
mod filesystem;
mod metadata;
mod network;
mod remote;
mod schedule;
mod storage;

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct FlakeArgs {
    /// address of the server
    #[clap(short, long)]
    address: String,

    /// Number of port
    #[clap(short, long, default_value_t = 8010)]
    port: u16,

    #[clap(short, long)]
    contacts: Vec<SocketAddr>,

    #[clap(short, long)]
    mountpoint: PathBuf,

    #[clap(short, long)]
    storage: Vec<PathBuf>,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing::subscriber::set_global_default(
        tracing_subscriber::FmtSubscriber::builder()
            .with_env_filter(
                tracing_subscriber::EnvFilter::from_default_env()
                    .add_directive("fuse3::raw::session=info".parse()?),
            )
            .with_timer(LocalTime::new(format_description!("[year]-[month]-[day] [hour]:[minute]:[second]")))
            .finish(),
    )?;

    let args: FlakeArgs = FlakeArgs::parse();
    trace!("{}:{}--->{:?}", args.address, args.port, args.storage);

    let addr = format!("{}:{}", args.address, args.port);
    let local = addr.parse::<SocketAddr>().unwrap();

    let uid = unsafe { libc::getuid() };
    let gid = unsafe { libc::getgid() };

    let f = File::open(args.mountpoint.clone()).await;
    if let Err(e) = f {
        let out = Command::new("umount")
            .arg(args.mountpoint.clone())
            .output()
            .expect("umount filesystem error.");
        error!("umount error, mountpoint: {}, {:?}", e, out);
    }
    let f = File::open(args.mountpoint.clone()).await.unwrap();
    let metadata = f.metadata().await.unwrap();
    let attr = Attribute::from(metadata);

    let schedule = Scheduler::setup(args.storage, local).await.unwrap();
    let schedule = Arc::new(schedule);
    network::run(schedule.clone(), local, &args.contacts).await;

    let storage = VFStorage::new(attr, schedule, uid, gid).await;
    let mut mount_options = MountOptions::default();
    mount_options.uid(uid).gid(gid).read_only(false);
    let r = Session::new(mount_options)
        .mount_with_unprivileged(storage, args.mountpoint)
        .await;
    match r {
        Ok(r) => {
            let r = r.await;
            if let Err(e) = r {
                error!("mount handle: {}", e);
            }
        }
        Err(e) => {
            error!("mount error: {}", e);
        }
    }

    Ok(())
}
